/******************************************************************************
 *
 *  Copyright (C) 1999-2012 Broadcom Corporation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at:
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 ******************************************************************************/

/******************************************************************************
 *
 *  This file contains the main SDP functions
 *
 ******************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "bt_common.h"
#include "bt_target.h"
#include "bt_utils.h"
#include "hcidefs.h"
#include "hcimsgs.h"

#include "l2c_api.h"
#include "l2cdefs.h"
#include "osi/include/osi.h"

#include "btm_api.h"
#include "btu.h"

#include "sdp_api.h"
#include "sdpint.h"

/******************************************************************************/
/*                     G L O B A L      S D P       D A T A                   */
/******************************************************************************/
tSDP_CB sdp_cb;

/******************************************************************************/
/*            L O C A L    F U N C T I O N     P R O T O T Y P E S            */
/******************************************************************************/
static void sdp_connect_ind(const RawAddress& bd_addr, uint16_t l2cap_cid,
                            UNUSED_ATTR uint16_t psm, uint8_t l2cap_id);
static void sdp_config_ind(uint16_t l2cap_cid, tL2CAP_CFG_INFO* p_cfg);
static void sdp_config_cfm(uint16_t l2cap_cid, tL2CAP_CFG_INFO* p_cfg);
static void sdp_disconnect_ind(uint16_t l2cap_cid, bool ack_needed);
static void sdp_data_ind(uint16_t l2cap_cid, BT_HDR* p_msg);

static void sdp_connect_cfm(uint16_t l2cap_cid, uint16_t result);
static void sdp_disconnect_cfm(uint16_t l2cap_cid, uint16_t result);

/*******************************************************************************
 *
 * Function         sdp_init
 *
 * Description      This function initializes the SDP unit.
 *
 * Returns          void
 *
 ******************************************************************************/
void sdp_init(void) {
  /* Clears all structures and local SDP database (if Server is enabled) */
  memset(&sdp_cb, 0, sizeof(tSDP_CB));

  for (int i = 0; i < SDP_MAX_CONNECTIONS; i++) {
    sdp_cb.ccb[i].sdp_conn_timer = alarm_new("sdp.sdp_conn_timer");
  }

  /* Initialize the L2CAP configuration. We only care about MTU and flush */
  sdp_cb.l2cap_my_cfg.mtu_present = true;
  sdp_cb.l2cap_my_cfg.mtu = SDP_MTU_SIZE;
  sdp_cb.l2cap_my_cfg.flush_to_present = true;
  sdp_cb.l2cap_my_cfg.flush_to = SDP_FLUSH_TO;

  sdp_cb.max_attr_list_size = SDP_MTU_SIZE - 16;
  sdp_cb.max_recs_per_search = SDP_MAX_DISC_SERVER_RECS;

#if (SDP_SERVER_ENABLED == TRUE)
  /* Register with Security Manager for the specific security level */
  if (!BTM_SetSecurityLevel(false, SDP_SERVICE_NAME, BTM_SEC_SERVICE_SDP_SERVER,
                            SDP_SECURITY_LEVEL, SDP_PSM, 0, 0)) {
    SDP_TRACE_ERROR("Security Registration Server failed");
    return;
  }
#endif

  /* Register with Security Manager for the specific security level */
  if (!BTM_SetSecurityLevel(true, SDP_SERVICE_NAME, BTM_SEC_SERVICE_SDP_SERVER,
                            SDP_SECURITY_LEVEL, SDP_PSM, 0, 0)) {
    SDP_TRACE_ERROR("Security Registration for Client failed");
    return;
  }

#if defined(SDP_INITIAL_TRACE_LEVEL)
  sdp_cb.trace_level = SDP_INITIAL_TRACE_LEVEL;
#else
  sdp_cb.trace_level = BT_TRACE_LEVEL_NONE; /* No traces */
#endif

  sdp_cb.reg_info.pL2CA_ConnectInd_Cb = sdp_connect_ind;
  sdp_cb.reg_info.pL2CA_ConnectCfm_Cb = sdp_connect_cfm;
  sdp_cb.reg_info.pL2CA_ConnectPnd_Cb = NULL;
  sdp_cb.reg_info.pL2CA_ConfigInd_Cb = sdp_config_ind;
  sdp_cb.reg_info.pL2CA_ConfigCfm_Cb = sdp_config_cfm;
  sdp_cb.reg_info.pL2CA_DisconnectInd_Cb = sdp_disconnect_ind;
  sdp_cb.reg_info.pL2CA_DisconnectCfm_Cb = sdp_disconnect_cfm;
  sdp_cb.reg_info.pL2CA_QoSViolationInd_Cb = NULL;
  sdp_cb.reg_info.pL2CA_DataInd_Cb = sdp_data_ind;
  sdp_cb.reg_info.pL2CA_CongestionStatus_Cb = NULL;
  sdp_cb.reg_info.pL2CA_TxComplete_Cb = NULL;

  /* Now, register with L2CAP */
  if (!L2CA_Register(SDP_PSM, &sdp_cb.reg_info)) {
    SDP_TRACE_ERROR("SDP Registration failed");
  }
}

void sdp_free(void) {
  for (int i = 0; i < SDP_MAX_CONNECTIONS; i++) {
    alarm_free(sdp_cb.ccb[i].sdp_conn_timer);
    sdp_cb.ccb[i].sdp_conn_timer = NULL;
  }
}

#if (SDP_DEBUG == TRUE)
/*******************************************************************************
 *
 * Function         sdp_set_max_attr_list_size
 *
 * Description      This function sets the max attribute list size to use
 *
 * Returns          void
 *
 ******************************************************************************/
uint16_t sdp_set_max_attr_list_size(uint16_t max_size) {
  if (max_size > (sdp_cb.l2cap_my_cfg.mtu - 16))
    max_size = sdp_cb.l2cap_my_cfg.mtu - 16;

  sdp_cb.max_attr_list_size = max_size;

  return sdp_cb.max_attr_list_size;
}
#endif

/*******************************************************************************
 *
 * Function         sdp_connect_ind
 *
 * Description      This function handles an inbound connection indication
 *                  from L2CAP. This is the case where we are acting as a
 *                  server.
 *
 * Returns          void
 *
 ******************************************************************************/
static void sdp_connect_ind(const RawAddress& bd_addr, uint16_t l2cap_cid,
                            UNUSED_ATTR uint16_t psm, uint8_t l2cap_id) {
#if (SDP_SERVER_ENABLED == TRUE)
  tCONN_CB* p_ccb;

  /* Allocate a new CCB. Return if none available. */
  p_ccb = sdpu_allocate_ccb();
  if (p_ccb == NULL) return;

  /* Transition to the next appropriate state, waiting for config setup. */
  p_ccb->con_state = SDP_STATE_CFG_SETUP;

  /* Save the BD Address and Channel ID. */
  p_ccb->device_address = bd_addr;
  p_ccb->connection_id = l2cap_cid;

  /* Send response to the L2CAP layer. */
  L2CA_ConnectRsp(bd_addr, l2cap_id, l2cap_cid, L2CAP_CONN_OK, L2CAP_CONN_OK);
  {
    tL2CAP_CFG_INFO cfg = sdp_cb.l2cap_my_cfg;

    if (cfg.fcr_present) {
      SDP_TRACE_DEBUG(
          "sdp_connect_ind:  mode %u, txwinsz %u, max_trans %u, rtrans_tout "
          "%u, mon_tout %u, mps %u",
          cfg.fcr.mode, cfg.fcr.tx_win_sz, cfg.fcr.max_transmit,
          cfg.fcr.rtrans_tout, cfg.fcr.mon_tout, cfg.fcr.mps);
    }

    if ((!L2CA_ConfigReq(l2cap_cid, &cfg)) && cfg.fcr_present &&
        cfg.fcr.mode != L2CAP_FCR_BASIC_MODE) {
      /* FCR not desired; try again in basic mode */
      cfg.fcr.mode = L2CAP_FCR_BASIC_MODE;
      cfg.fcr_present = false;
      L2CA_ConfigReq(l2cap_cid, &cfg);
    }
  }

  SDP_TRACE_EVENT("SDP - Rcvd L2CAP conn ind, sent config req, CID 0x%x",
                  p_ccb->connection_id);
#else /* No server */
  /* Reject the connection */
  L2CA_ConnectRsp(bd_addr, l2cap_id, l2cap_cid, L2CAP_CONN_NO_PSM, 0);
#endif
}

/*******************************************************************************
 *
 * Function         sdp_connect_cfm
 *
 * Description      This function handles the connect confirm events
 *                  from L2CAP. This is the case when we are acting as a
 *                  client and have sent a connect request.
 *
 * Returns          void
 *
 ******************************************************************************/
static void sdp_connect_cfm(uint16_t l2cap_cid, uint16_t result) {
  tCONN_CB* p_ccb;
  tL2CAP_CFG_INFO cfg;

  /* Find CCB based on CID */
  p_ccb = sdpu_find_ccb_by_cid(l2cap_cid);
  if (p_ccb == NULL) {
    SDP_TRACE_WARNING("SDP - Rcvd conn cnf for unknown CID 0x%x", l2cap_cid);
    return;
  }

  /* If the connection response contains success status, then */
  /* Transition to the next state and startup the timer.      */
  if ((result == L2CAP_CONN_OK) && (p_ccb->con_state == SDP_STATE_CONN_SETUP)) {
    p_ccb->con_state = SDP_STATE_CFG_SETUP;

    cfg = sdp_cb.l2cap_my_cfg;

    if (cfg.fcr_present) {
      SDP_TRACE_DEBUG(
          "sdp_connect_cfm:  mode %u, txwinsz %u, max_trans %u, rtrans_tout "
          "%u, mon_tout %u, mps %u",
          cfg.fcr.mode, cfg.fcr.tx_win_sz, cfg.fcr.max_transmit,
          cfg.fcr.rtrans_tout, cfg.fcr.mon_tout, cfg.fcr.mps);
    }

    if ((!L2CA_ConfigReq(l2cap_cid, &cfg)) && cfg.fcr_present &&
        cfg.fcr.mode != L2CAP_FCR_BASIC_MODE) {
      /* FCR not desired; try again in basic mode */
      cfg.fcr_present = false;
      cfg.fcr.mode = L2CAP_FCR_BASIC_MODE;
      L2CA_ConfigReq(l2cap_cid, &cfg);
    }

    SDP_TRACE_EVENT("SDP - got conn cnf, sent cfg req, CID: 0x%x",
                    p_ccb->connection_id);
  } else {
    SDP_TRACE_WARNING("SDP - Rcvd conn cnf with error: 0x%x  CID 0x%x", result,
                      p_ccb->connection_id);

    /* Tell the user if he has a callback */
    if (p_ccb->p_cb || p_ccb->p_cb2) {
      uint16_t err = -1;
      if ((result == HCI_ERR_HOST_REJECT_SECURITY) ||
          (result == HCI_ERR_AUTH_FAILURE) ||
          (result == HCI_ERR_PAIRING_NOT_ALLOWED) ||
          (result == HCI_ERR_PAIRING_WITH_UNIT_KEY_NOT_SUPPORTED) ||
          (result == HCI_ERR_KEY_MISSING))
        err = SDP_SECURITY_ERR;
      else if (result == HCI_ERR_HOST_REJECT_DEVICE)
        err = SDP_CONN_REJECTED;
      else
        err = SDP_CONN_FAILED;
      if (p_ccb->p_cb)
        (*p_ccb->p_cb)(err);
      else if (p_ccb->p_cb2)
        (*p_ccb->p_cb2)(err, p_ccb->user_data);
    }
    sdpu_release_ccb(p_ccb);
  }
}

/*******************************************************************************
 *
 * Function         sdp_config_ind
 *
 * Description      This function processes the L2CAP configuration indication
 *                  event.
 *
 * Returns          void
 *
 ******************************************************************************/
static void sdp_config_ind(uint16_t l2cap_cid, tL2CAP_CFG_INFO* p_cfg) {
  tCONN_CB* p_ccb;

  /* Find CCB based on CID */
  p_ccb = sdpu_find_ccb_by_cid(l2cap_cid);
  if (p_ccb == NULL) {
    SDP_TRACE_WARNING("SDP - Rcvd L2CAP cfg ind, unknown CID: 0x%x", l2cap_cid);
    return;
  }

  /* Remember the remote MTU size */
  if (!p_cfg->mtu_present) {
    /* use min(L2CAP_DEFAULT_MTU,SDP_MTU_SIZE) for GKI buffer size reasons */
    p_ccb->rem_mtu_size =
        (L2CAP_DEFAULT_MTU > SDP_MTU_SIZE) ? SDP_MTU_SIZE : L2CAP_DEFAULT_MTU;
  } else {
    if (p_cfg->mtu > SDP_MTU_SIZE)
      p_ccb->rem_mtu_size = SDP_MTU_SIZE;
    else
      p_ccb->rem_mtu_size = p_cfg->mtu;
  }

  /* For now, always accept configuration from the other side */
  p_cfg->flush_to_present = false;
  p_cfg->mtu_present = false;
  p_cfg->result = L2CAP_CFG_OK;

  /* Check peer config request against our rfcomm configuration */
  if (p_cfg->fcr_present) {
    /* Reject the window size if it is bigger than we want it to be */
    if (p_cfg->fcr.mode != L2CAP_FCR_BASIC_MODE) {
      if (sdp_cb.l2cap_my_cfg.fcr.mode != L2CAP_FCR_BASIC_MODE &&
          p_cfg->fcr.tx_win_sz > sdp_cb.l2cap_my_cfg.fcr.tx_win_sz) {
        p_cfg->fcr.tx_win_sz = sdp_cb.l2cap_my_cfg.fcr.tx_win_sz;
        p_cfg->result = L2CAP_CFG_UNACCEPTABLE_PARAMS;
        SDP_TRACE_DEBUG(
            "sdp_config_ind(CONFIG) -> Please try again with SMALLER TX "
            "WINDOW");
      }

      /* Reject if locally we want basic and they don't */
      if (sdp_cb.l2cap_my_cfg.fcr.mode == L2CAP_FCR_BASIC_MODE) {
        /* Ask for a new setup */
        p_cfg->fcr.mode = L2CAP_FCR_BASIC_MODE;
        p_cfg->result = L2CAP_CFG_UNACCEPTABLE_PARAMS;
        SDP_TRACE_DEBUG(
            "sdp_config_ind(CONFIG) -> Please try again with BASIC mode");
      }
      /* Remain in configure state and give the peer our desired configuration
       */
      if (p_cfg->result != L2CAP_CFG_OK) {
        SDP_TRACE_WARNING(
            "SDP - Rcvd cfg ind, Unacceptable Parameters sent cfg cfm, CID: "
            "0x%x",
            l2cap_cid);
        L2CA_ConfigRsp(l2cap_cid, p_cfg);
        return;
      }
    } else /* We agree with peer's request */
      p_cfg->fcr_present = false;
  }

  L2CA_ConfigRsp(l2cap_cid, p_cfg);

  SDP_TRACE_EVENT("SDP - Rcvd cfg ind, sent cfg cfm, CID: 0x%x", l2cap_cid);

  p_ccb->con_flags |= SDP_FLAGS_HIS_CFG_DONE;

  if (p_ccb->con_flags & SDP_FLAGS_MY_CFG_DONE) {
    p_ccb->con_state = SDP_STATE_CONNECTED;

    if (p_ccb->con_flags & SDP_FLAGS_IS_ORIG) {
      sdp_disc_connected(p_ccb);
    } else {
      /* Start inactivity timer */
      alarm_set_on_mloop(p_ccb->sdp_conn_timer, SDP_INACT_TIMEOUT_MS,
                         sdp_conn_timer_timeout, p_ccb);
    }
  }
}

/*******************************************************************************
 *
 * Function         sdp_config_cfm
 *
 * Description      This function processes the L2CAP configuration confirmation
 *                  event.
 *
 * Returns          void
 *
 ******************************************************************************/
static void sdp_config_cfm(uint16_t l2cap_cid, tL2CAP_CFG_INFO* p_cfg) {
  tCONN_CB* p_ccb;

  SDP_TRACE_EVENT("SDP - Rcvd cfg cfm, CID: 0x%x  Result: %d", l2cap_cid,
                  p_cfg->result);

  /* Find CCB based on CID */
  p_ccb = sdpu_find_ccb_by_cid(l2cap_cid);
  if (p_ccb == NULL) {
    SDP_TRACE_WARNING("SDP - Rcvd L2CAP cfg ind, unknown CID: 0x%x", l2cap_cid);
    return;
  }

  /* For now, always accept configuration from the other side */
  if (p_cfg->result == L2CAP_CFG_OK) {
    p_ccb->con_flags |= SDP_FLAGS_MY_CFG_DONE;

    if (p_ccb->con_flags & SDP_FLAGS_HIS_CFG_DONE) {
      p_ccb->con_state = SDP_STATE_CONNECTED;

      if (p_ccb->con_flags & SDP_FLAGS_IS_ORIG) {
        sdp_disc_connected(p_ccb);
      } else {
        /* Start inactivity timer */
        alarm_set_on_mloop(p_ccb->sdp_conn_timer, SDP_INACT_TIMEOUT_MS,
                           sdp_conn_timer_timeout, p_ccb);
      }
    }
  } else {
    /* If peer has rejected FCR and suggested basic then try basic */
    if (p_cfg->fcr_present) {
      tL2CAP_CFG_INFO cfg = sdp_cb.l2cap_my_cfg;
      cfg.fcr_present = false;
      L2CA_ConfigReq(l2cap_cid, &cfg);

      /* Remain in configure state */
      return;
    }

    sdp_disconnect(p_ccb, SDP_CFG_FAILED);
  }
}

/*******************************************************************************
 *
 * Function         sdp_disconnect_ind
 *
 * Description      This function handles a disconnect event from L2CAP. If
 *                  requested to, we ack the disconnect before dropping the CCB
 *
 * Returns          void
 *
 ******************************************************************************/
static void sdp_disconnect_ind(uint16_t l2cap_cid, bool ack_needed) {
  tCONN_CB* p_ccb;

  /* Find CCB based on CID */
  p_ccb = sdpu_find_ccb_by_cid(l2cap_cid);
  if (p_ccb == NULL) {
    SDP_TRACE_WARNING("SDP - Rcvd L2CAP disc, unknown CID: 0x%x", l2cap_cid);
    return;
  }

  if (ack_needed) L2CA_DisconnectRsp(l2cap_cid);

  SDP_TRACE_EVENT("SDP - Rcvd L2CAP disc, CID: 0x%x", l2cap_cid);
  /* Tell the user if he has a callback */
  if (p_ccb->p_cb)
    (*p_ccb->p_cb)((uint16_t)((p_ccb->con_state == SDP_STATE_CONNECTED)
                                  ? SDP_SUCCESS
                                  : SDP_CONN_FAILED));
  else if (p_ccb->p_cb2)
    (*p_ccb->p_cb2)(
        (uint16_t)((p_ccb->con_state == SDP_STATE_CONNECTED) ? SDP_SUCCESS
                                                             : SDP_CONN_FAILED),
        p_ccb->user_data);

  sdpu_release_ccb(p_ccb);
}

/*******************************************************************************
 *
 * Function         sdp_data_ind
 *
 * Description      This function is called when data is received from L2CAP.
 *                  if we are the originator of the connection, we are the SDP
 *                  client, and the received message is queued for the client.
 *
 *                  If we are the destination of the connection, we are the SDP
 *                  server, so the message is passed to the server processing
 *                  function.
 *
 * Returns          void
 *
 ******************************************************************************/
static void sdp_data_ind(uint16_t l2cap_cid, BT_HDR* p_msg) {
  tCONN_CB* p_ccb;

  /* Find CCB based on CID */
  p_ccb = sdpu_find_ccb_by_cid(l2cap_cid);
  if (p_ccb != NULL) {
    if (p_ccb->con_state == SDP_STATE_CONNECTED) {
      if (p_ccb->con_flags & SDP_FLAGS_IS_ORIG)
        sdp_disc_server_rsp(p_ccb, p_msg);
      else
        sdp_server_handle_client_req(p_ccb, p_msg);
    } else {
      SDP_TRACE_WARNING(
          "SDP - Ignored L2CAP data while in state: %d, CID: 0x%x",
          p_ccb->con_state, l2cap_cid);
    }
  } else {
    SDP_TRACE_WARNING("SDP - Rcvd L2CAP data, unknown CID: 0x%x", l2cap_cid);
  }

  osi_free(p_msg);
}

/*******************************************************************************
 *
 * Function         sdp_conn_originate
 *
 * Description      This function is called from the API to originate a
 *                  connection.
 *
 * Returns          void
 *
 ******************************************************************************/
tCONN_CB* sdp_conn_originate(const RawAddress& p_bd_addr) {
  tCONN_CB* p_ccb;
  uint16_t cid;

  /* Allocate a new CCB. Return if none available. */
  p_ccb = sdpu_allocate_ccb();
  if (p_ccb == NULL) {
    SDP_TRACE_WARNING("SDP - no spare CCB for orig");
    return (NULL);
  }

  SDP_TRACE_EVENT("SDP - Originate started");

  /* We are the originator of this connection */
  p_ccb->con_flags |= SDP_FLAGS_IS_ORIG;

  /* Save the BD Address and Channel ID. */
  p_ccb->device_address = p_bd_addr;
  ;

  /* Transition to the next appropriate state, waiting for connection confirm.
   */
  p_ccb->con_state = SDP_STATE_CONN_SETUP;

  cid = L2CA_ConnectReq(SDP_PSM, p_bd_addr);

  /* Check if L2CAP started the connection process */
  if (cid != 0) {
    p_ccb->connection_id = cid;

    return (p_ccb);
  } else {
    SDP_TRACE_WARNING("SDP - Originate failed");
    sdpu_release_ccb(p_ccb);
    return (NULL);
  }
}

/*******************************************************************************
 *
 * Function         sdp_disconnect
 *
 * Description      This function disconnects a connection.
 *
 * Returns          void
 *
 ******************************************************************************/
void sdp_disconnect(tCONN_CB* p_ccb, uint16_t reason) {
#if (SDP_BROWSE_PLUS == TRUE)

  /* If we are browsing for multiple UUIDs ... */
  if ((p_ccb->con_state == SDP_STATE_CONNECTED) &&
      (p_ccb->con_flags & SDP_FLAGS_IS_ORIG) &&
      ((reason == SDP_SUCCESS) || (reason == SDP_NO_RECS_MATCH))) {
    /* If the browse found something, do no more searching */
    if ((p_ccb->cur_uuid_idx == 0) && (p_ccb->p_db->p_first_rec))
      p_ccb->cur_uuid_idx = p_ccb->p_db->num_uuid_filters;

    while (++p_ccb->cur_uuid_idx < p_ccb->p_db->num_uuid_filters) {
      /* Check we have not already found the UUID (maybe through browse) */
      if ((p_ccb->p_db->uuid_filters[p_ccb->cur_uuid_idx].len == 2) &&
          (SDP_FindServiceInDb(
              p_ccb->p_db,
              p_ccb->p_db->uuid_filters[p_ccb->cur_uuid_idx].uu.uuid16, NULL)))
        continue;

      if ((p_ccb->p_db->uuid_filters[p_ccb->cur_uuid_idx].len > 2) &&
          (SDP_FindServiceUUIDInDb(
              p_ccb->p_db, &p_ccb->p_db->uuid_filters[p_ccb->cur_uuid_idx],
              NULL)))
        continue;

      p_ccb->cur_handle = 0;

      SDP_TRACE_EVENT("SDP - looking for for more,  CID: 0x%x",
                      p_ccb->connection_id);

      sdp_disc_connected(p_ccb);
      return;
    }
  }

  if ((reason == SDP_NO_RECS_MATCH) && (p_ccb->p_db->p_first_rec))
    reason = SDP_SUCCESS;

#endif

  SDP_TRACE_EVENT("SDP - disconnect  CID: 0x%x", p_ccb->connection_id);

  /* Check if we have a connection ID */
  if (p_ccb->connection_id != 0) {
    L2CA_DisconnectReq(p_ccb->connection_id);
    p_ccb->disconnect_reason = reason;
  }

  /* If at setup state, we may not get callback ind from L2CAP */
  /* Call user callback immediately */
  if (p_ccb->con_state == SDP_STATE_CONN_SETUP) {
    /* Tell the user if he has a callback */
    if (p_ccb->p_cb)
      (*p_ccb->p_cb)(reason);
    else if (p_ccb->p_cb2)
      (*p_ccb->p_cb2)(reason, p_ccb->user_data);

    sdpu_release_ccb(p_ccb);
  }
}

/*******************************************************************************
 *
 * Function         sdp_disconnect_cfm
 *
 * Description      This function handles a disconnect confirm event from L2CAP.
 *
 * Returns          void
 *
 ******************************************************************************/
static void sdp_disconnect_cfm(uint16_t l2cap_cid,
                               UNUSED_ATTR uint16_t result) {
  tCONN_CB* p_ccb;

  /* Find CCB based on CID */
  p_ccb = sdpu_find_ccb_by_cid(l2cap_cid);
  if (p_ccb == NULL) {
    SDP_TRACE_WARNING("SDP - Rcvd L2CAP disc cfm, unknown CID: 0x%x",
                      l2cap_cid);
    return;
  }

  SDP_TRACE_EVENT("SDP - Rcvd L2CAP disc cfm, CID: 0x%x", l2cap_cid);

  /* Tell the user if he has a callback */
  if (p_ccb->p_cb)
    (*p_ccb->p_cb)(p_ccb->disconnect_reason);
  else if (p_ccb->p_cb2)
    (*p_ccb->p_cb2)(p_ccb->disconnect_reason, p_ccb->user_data);

  sdpu_release_ccb(p_ccb);
}


/*******************************************************************************
 *
 * Function         sdp_conn_timer_timeout
 *
 * Description      This function processes a timeout. Currently, we simply send
 *                  a disconnect request to L2CAP.
 *
 * Returns          void
 *
 ******************************************************************************/
void sdp_conn_timer_timeout(void* data) {
  tCONN_CB* p_ccb = (tCONN_CB*)data;

  SDP_TRACE_EVENT("SDP - CCB timeout in state: %d  CID: 0x%x", p_ccb->con_state,
                  p_ccb->connection_id);

  L2CA_DisconnectReq(p_ccb->connection_id);
  /* Tell the user if he has a callback */
  if (p_ccb->p_cb)
    (*p_ccb->p_cb)(SDP_CONN_FAILED);
  else if (p_ccb->p_cb2)
    (*p_ccb->p_cb2)(SDP_CONN_FAILED, p_ccb->user_data);
  sdpu_release_ccb(p_ccb);
}
